2 * This is JsJpegMeta v1.0
3 * From: https://code.google.com/p/jsjpegmeta/downloads/list
4 * From: https://github.com/bennoleslie/jsjpegmeta/blob/v1.0.0/jpegmeta.js
6 * Ported to MediaWiki ResourceLoader by Bryan Tong Minh
9 * - Add this.JpegMeta assignment to expose it as global.
10 * - Add export as module.
11 * - Add mw.libs.jpegmeta wrapper.
16 Copyright (c) 2009 Ben Leslie
18 Permission is hereby granted, free of charge, to any person obtaining a copy
19 of this software and associated documentation files (the "Software"), to deal
20 in the Software without restriction, including without limitation the rights
21 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
22 copies of the Software, and to permit persons to whom the Software is
23 furnished to do so, subject to the following conditions:
25 The above copyright notice and this permission notice shall be included in
26 all copies or substantial portions of the Software.
28 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
29 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
30 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
31 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
32 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
33 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
38 This JavaScript library is used to parse meta-data from files
39 with mime-type image/jpeg.
41 Include it with something like:
43 <script type="text/javascript" src="jpegmeta.js"></script>
45 This adds a single 'module' object called 'JpegMeta' to the global
50 JpegMeta.parseNum - parse unsigned integers from binary data
51 JpegMeta.parseSnum - parse signed integers from binary data
55 JpegMeta.Rational - A rational number class
58 JpegMeta.JpegFile - Primary class for Javascript parsing
62 // MediaWiki: Expose as global
63 this.JpegMeta
= JpegMeta
;
66 parse an unsigned number of size bytes at offset in some binary string data.
68 is "<" parse the data as little endian, if endian
69 is ">" parse as big-endian.
71 JpegMeta
.parseNum
= function parseNum(endian
, data
, offset
, size
) {
74 var big_endian
= (endian
=== ">");
75 if (offset
=== undefined) offset
= 0;
76 if (size
=== undefined) size
= data
.length
- offset
;
77 for (big_endian
? i
= offset
: i
= offset
+ size
- 1;
78 big_endian
? i
< offset
+ size
: i
>= offset
;
79 big_endian
? i
++ : i
--) {
81 ret
+= data
.charCodeAt(i
);
87 parse an signed number of size bytes at offset in some binary string data.
89 is "<" parse the data as little endian, if endian
90 is ">" parse as big-endian.
92 JpegMeta
.parseSnum
= function parseSnum(endian
, data
, offset
, size
) {
96 var big_endian
= (endian
=== ">");
97 if (offset
=== undefined) offset
= 0;
98 if (size
=== undefined) size
= data
.length
- offset
;
99 for (big_endian
? i
= offset
: i
= offset
+ size
- 1;
100 big_endian
? i
< offset
+ size
: i
>= offset
;
101 big_endian
? i
++ : i
--) {
102 if (neg
=== undefined) {
103 /* Negative if top bit is set */
104 neg
= (data
.charCodeAt(i
) & 0x80) === 0x80;
107 /* If it is negative we invert the bits */
108 ret
+= neg
? ~data
.charCodeAt(i
) & 0xff: data
.charCodeAt(i
);
111 /* If it is negative we do two's complement */
118 /* Rational number class */
119 JpegMeta
.Rational
= function Rational(num
, den
)
126 /* Rational number methods */
127 JpegMeta
.Rational
.prototype.toString
= function toString() {
128 if (this.num
=== 0) {
129 return "" + this.num
;
131 if (this.den
=== 1) {
132 return "" + this.num
;
134 if (this.num
=== 1) {
135 return this.num
+ " / " + this.den
;
137 return this.num
/ this.den
; // + "/" + this.den;
140 JpegMeta
.Rational
.prototype.asFloat
= function asFloat() {
141 return this.num
/ this.den
;
144 /* MetaGroup class */
145 JpegMeta
.MetaGroup
= function MetaGroup(fieldName
, description
) {
146 this.fieldName
= fieldName
;
147 this.description
= description
;
152 JpegMeta
.MetaGroup
.prototype._addProperty
= function _addProperty(fieldName
, description
, value
) {
153 var property
= new JpegMeta
.MetaProp(fieldName
, description
, value
);
154 this[property
.fieldName
] = property
;
155 this.metaProps
[property
.fieldName
] = property
;
158 JpegMeta
.MetaGroup
.prototype.toString
= function toString() {
159 return "[MetaGroup " + this.description
+ "]";
163 JpegMeta
.MetaProp
= function MetaProp(fieldName
, description
, value
) {
164 this.fieldName
= fieldName
;
165 this.description
= description
;
170 JpegMeta
.MetaProp
.prototype.toString
= function toString() {
171 return "" + this.value
;
175 JpegMeta
.JpegFile
= function JpegFile(binary_data
, filename
) {
176 /* Change this to EOI if we want to parse. */
177 var break_segment
= this._SOS
;
179 this.metaGroups
= {};
180 this._binary_data
= binary_data
;
181 this.filename
= filename
;
183 /* Go through and parse. */
185 var pos_start_of_segment
= 0;
194 /* Check to see if this looks like a JPEG file */
195 if (this._binary_data
.slice(0, 2) !== this._SOI_MARKER
) {
196 throw new Error("Doesn't look like a JPEG file. First two bytes are " +
197 this._binary_data
.charCodeAt(0) + "," +
198 this._binary_data
.charCodeAt(1) + ".");
203 while (pos
< this._binary_data
.length
) {
204 delim
= this._binary_data
.charCodeAt(pos
++);
205 mark
= this._binary_data
.charCodeAt(pos
++);
207 pos_start_of_segment
= pos
;
209 if (delim
!= this._DELIM
) {
213 if (mark
=== break_segment
) {
217 headersize
= JpegMeta
.parseNum(">", this._binary_data
, pos
, 2);
221 while (pos
< this._binary_data
.length
) {
222 delim
= this._binary_data
.charCodeAt(pos
++);
223 if (delim
== this._DELIM
) {
224 _mark
= this._binary_data
.charCodeAt(pos
++);
232 segsize
= pos
- pos_start_of_segment
;
234 if (this._markers
[mark
]) {
235 mark_code
= this._markers
[mark
][0];
236 mark_fn
= this._markers
[mark
][1];
243 this[mark_fn
](mark
, pos_start_of_segment
+ 2);
248 if (this.general
=== undefined) {
249 throw Error("Invalid JPEG file.");
255 this.JpegMeta
.JpegFile
.prototype.toString = function () {
256 return "[JpegFile " + this.filename
+ " " +
257 this.general
.type
+ " " +
258 this.general
.pixelWidth
+ "x" +
259 this.general
.pixelHeight
+
260 " Depth: " + this.general
.depth
+ "]";
263 /* Some useful constants */
264 this.JpegMeta
.JpegFile
.prototype._SOI_MARKER
= '\xff\xd8';
265 this.JpegMeta
.JpegFile
.prototype._DELIM
= 0xff;
266 this.JpegMeta
.JpegFile
.prototype._EOI
= 0xd9;
267 this.JpegMeta
.JpegFile
.prototype._SOS
= 0xda;
269 this.JpegMeta
.JpegFile
.prototype._sofHandler
= function _sofHandler (mark
, pos
) {
270 if (this.general
!== undefined) {
271 throw Error("Unexpected multiple-frame image");
274 this._addMetaGroup("general", "General");
275 this.general
._addProperty("depth", "Depth", JpegMeta
.parseNum(">", this._binary_data
, pos
, 1));
276 this.general
._addProperty("pixelHeight", "Pixel Height", JpegMeta
.parseNum(">", this._binary_data
, pos
+ 1, 2));
277 this.general
._addProperty("pixelWidth", "Pixel Width",JpegMeta
.parseNum(">", this._binary_data
, pos
+ 3, 2));
278 this.general
._addProperty("type", "Type", this._markers
[mark
][2]);
282 this.JpegMeta
.JpegFile
.prototype._JFIF_IDENT
= "JFIF\x00";
283 this.JpegMeta
.JpegFile
.prototype._JFXX_IDENT
= "JFXX\x00";
286 this.JpegMeta
.JpegFile
.prototype._EXIF_IDENT
= "Exif\x00";
289 this.JpegMeta
.JpegFile
.prototype._types
= {
290 /* The format is identifier : ["type name", type_size_in_bytes ] */
297 7 : ["UNDEFINED", 1],
300 10 : ["SRATIONAL", 8],
305 this.JpegMeta
.JpegFile
.prototype._tifftags
= {
306 /* A. Tags relating to image data structure */
307 256 : ["Image width", "ImageWidth"],
308 257 : ["Image height", "ImageLength"],
309 258 : ["Number of bits per component", "BitsPerSample"],
310 259 : ["Compression scheme", "Compression",
311 {1 : "uncompressed", 6 : "JPEG compression" }],
312 262 : ["Pixel composition", "PhotmetricInerpretation",
313 {2 : "RGB", 6 : "YCbCr"}],
314 274 : ["Orientation of image", "Orientation",
315 /* FIXME: Check the mirror-image / reverse encoding and rotation */
316 {1 : "Normal", 2 : "Reverse?",
317 3 : "Upside-down", 4 : "Upside-down Reverse",
318 5 : "90 degree CW", 6 : "90 degree CW reverse",
319 7 : "90 degree CCW", 8 : "90 degree CCW reverse"}],
320 277 : ["Number of components", "SamplesPerPixel"],
321 284 : ["Image data arrangement", "PlanarConfiguration",
322 {1 : "chunky format", 2 : "planar format"}],
323 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
324 531 : ["Y and C positioning", "YCbCrPositioning",
325 {1 : "centered", 2 : "co-sited"}],
326 282 : ["X Resolution", "XResolution"],
327 283 : ["Y Resolution", "YResolution"],
328 296 : ["Resolution Unit", "ResolutionUnit",
329 {2 : "inches", 3 : "centimeters"}],
330 /* B. Tags realting to recording offset */
331 273 : ["Image data location", "StripOffsets"],
332 278 : ["Number of rows per strip", "RowsPerStrip"],
333 279 : ["Bytes per compressed strip", "StripByteCounts"],
334 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
335 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
336 /* C. Tags relating to image data characteristics */
337 301 : ["Transfer function", "TransferFunction"],
338 318 : ["White point chromaticity", "WhitePoint"],
339 319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
340 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
341 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
343 306 : ["Date and time", "DateTime"],
344 270 : ["Image title", "ImageDescription"],
345 271 : ["Make", "Make"],
346 272 : ["Model", "Model"],
347 305 : ["Software", "Software"],
348 315 : ["Person who created the image", "Artist"],
349 316 : ["Host Computer", "HostComputer"],
350 33432 : ["Copyright holder", "Copyright"],
352 34665 : ["Exif tag", "ExifIfdPointer"],
353 34853 : ["GPS tag", "GPSInfoIfdPointer"]
356 this.JpegMeta
.JpegFile
.prototype._exiftags
= {
357 /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
358 /* A. Tags Relating to Version */
359 36864 : ["Exif Version", "ExifVersion"],
360 40960 : ["FlashPix Version", "FlashpixVersion"],
362 /* B. Tag Relating to Image Data Characteristics */
363 40961 : ["Color Space", "ColorSpace"],
365 /* C. Tags Relating to Image Configuration */
366 37121 : ["Meaning of each component", "ComponentsConfiguration"],
367 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
368 40962 : ["Pixel X Dimension", "PixelXDimension"],
369 40963 : ["Pixel Y Dimension", "PixelYDimension"],
371 /* D. Tags Relating to User Information */
372 37500 : ["Manufacturer notes", "MakerNote"],
373 37510 : ["User comments", "UserComment"],
375 /* E. Tag Relating to Related File Information */
376 40964 : ["Related audio file", "RelatedSoundFile"],
378 /* F. Tags Relating to Date and Time */
379 36867 : ["Date Time Original", "DateTimeOriginal"],
380 36868 : ["Date Time Digitized", "DateTimeDigitized"],
381 37520 : ["DateTime subseconds", "SubSecTime"],
382 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
383 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
385 /* G. Tags Relating to Picture-Taking Conditions */
386 33434 : ["Exposure time", "ExposureTime"],
387 33437 : ["FNumber", "FNumber"],
388 34850 : ["Exposure program", "ExposureProgram"],
389 34852 : ["Spectral sensitivity", "SpectralSensitivity"],
390 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
391 34856 : ["Optoelectric coefficient", "OECF"],
392 37377 : ["Shutter Speed", "ShutterSpeedValue"],
393 37378 : ["Aperture Value", "ApertureValue"],
394 37379 : ["Brightness", "BrightnessValue"],
395 37380 : ["Exposure Bias Value", "ExposureBiasValue"],
396 37381 : ["Max Aperture Value", "MaxApertureValue"],
397 37382 : ["Subject Distance", "SubjectDistance"],
398 37383 : ["Metering Mode", "MeteringMode"],
399 37384 : ["Light Source", "LightSource"],
400 37385 : ["Flash", "Flash"],
401 37386 : ["Focal Length", "FocalLength"],
402 37396 : ["Subject Area", "SubjectArea"],
403 41483 : ["Flash Energy", "FlashEnergy"],
404 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
405 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
406 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
407 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
408 41492 : ["Subject Location", "SubjectLocation"],
409 41493 : ["Exposure Index", "ExposureIndex"],
410 41495 : ["Sensing Method", "SensingMethod"],
411 41728 : ["File Source", "FileSource"],
412 41729 : ["Scene Type", "SceneType"],
413 41730 : ["CFA Pattern", "CFAPattern"],
414 41985 : ["Custom Rendered", "CustomRendered"],
415 41986 : ["Exposure Mode", "Exposure Mode"],
416 41987 : ["White Balance", "WhiteBalance"],
417 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
418 41990 : ["Scene Capture Type", "SceneCaptureType"],
419 41991 : ["Gain Control", "GainControl"],
420 41992 : ["Contrast", "Contrast"],
421 41993 : ["Saturation", "Saturation"],
422 41994 : ["Sharpness", "Sharpness"],
423 41995 : ["Device settings description", "DeviceSettingDescription"],
424 41996 : ["Subject distance range", "SubjectDistanceRange"],
427 42016 : ["Unique image ID", "ImageUniqueID"],
429 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"]
432 this.JpegMeta
.JpegFile
.prototype._gpstags
= {
433 /* A. Tags Relating to GPS */
434 0 : ["GPS tag version", "GPSVersionID"],
435 1 : ["North or South Latitude", "GPSLatitudeRef"],
436 2 : ["Latitude", "GPSLatitude"],
437 3 : ["East or West Longitude", "GPSLongitudeRef"],
438 4 : ["Longitude", "GPSLongitude"],
439 5 : ["Altitude reference", "GPSAltitudeRef"],
440 6 : ["Altitude", "GPSAltitude"],
441 7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
442 8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
443 9 : ["GPS receiver status", "GPSStatus"],
444 10 : ["GPS mesaurement mode", "GPSMeasureMode"],
445 11 : ["Measurement precision", "GPSDOP"],
446 12 : ["Speed unit", "GPSSpeedRef"],
447 13 : ["Speed of GPS receiver", "GPSSpeed"],
448 14 : ["Reference for direction of movement", "GPSTrackRef"],
449 15 : ["Direction of movement", "GPSTrack"],
450 16 : ["Reference for direction of image", "GPSImgDirectionRef"],
451 17 : ["Direction of image", "GPSImgDirection"],
452 18 : ["Geodetic survey data used", "GPSMapDatum"],
453 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
454 20 : ["Latitude of destination", "GPSDestLatitude"],
455 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
456 22 : ["Longitude of destination", "GPSDestLongitude"],
457 23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
458 24 : ["Bearing of destination", "GPSDestBearing"],
459 25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
460 26 : ["Distance to destination", "GPSDestDistance"],
461 27 : ["Name of GPS processing method", "GPSProcessingMethod"],
462 28 : ["Name of GPS area", "GPSAreaInformation"],
463 29 : ["GPS Date", "GPSDateStamp"],
464 30 : ["GPS differential correction", "GPSDifferential"]
467 this.JpegMeta
.JpegFile
.prototype._markers
= {
468 /* Start Of Frame markers, non-differential, Huffman coding */
469 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
470 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
471 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
472 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
474 /* Start Of Frame markers, differential, Huffman coding */
475 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
476 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
477 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
479 /* Start Of Frame markers, non-differential, arithmetic coding */
480 0xc8: ["JPG", null, "Reserved for JPEG extensions"],
481 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
482 0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
483 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
485 /* Start Of Frame markers, differential, arithmetic coding */
486 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
487 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
488 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
490 /* Huffman table specification */
491 0xc4: ["DHT", null, "Define Huffman table(s)"],
492 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
494 /* Restart interval termination" */
495 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
496 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
497 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
498 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
499 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
500 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
501 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
502 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
505 0xd8: ["SOI", null, "Start of image"],
506 0xd9: ["EOI", null, "End of image"],
507 0xda: ["SOS", null, "Start of scan"],
508 0xdb: ["DQT", null, "Define quantization table(s)"],
509 0xdc: ["DNL", null, "Define number of lines"],
510 0xdd: ["DRI", null, "Define restart interval"],
511 0xde: ["DHP", null, "Define hierarchical progression"],
512 0xdf: ["EXP", null, "Expand reference component(s)"],
513 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
514 0xe1: ["APP1", "_app1Handler"],
515 0xe2: ["APP2", null],
516 0xe3: ["APP3", null],
517 0xe4: ["APP4", null],
518 0xe5: ["APP5", null],
519 0xe6: ["APP6", null],
520 0xe7: ["APP7", null],
521 0xe8: ["APP8", null],
522 0xe9: ["APP9", null],
523 0xea: ["APP10", null],
524 0xeb: ["APP11", null],
525 0xec: ["APP12", null],
526 0xed: ["APP13", null],
527 0xee: ["APP14", null],
528 0xef: ["APP15", null],
529 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
530 0xf1: ["JPG1", null],
531 0xf2: ["JPG2", null],
532 0xf3: ["JPG3", null],
533 0xf4: ["JPG4", null],
534 0xf5: ["JPG5", null],
535 0xf6: ["JPG6", null],
536 0xf7: ["JPG7", null],
537 0xf8: ["JPG8", null],
538 0xf9: ["JPG9", null],
539 0xfa: ["JPG10", null],
540 0xfb: ["JPG11", null],
541 0xfc: ["JPG12", null],
542 0xfd: ["JPG13", null],
543 0xfe: ["COM", null], /* Comment */
545 /* Reserved markers */
546 0x01: ["JPG13", null] /* For temporary private use in arithmetic coding */
547 /* 02 -> bf are reserverd */
550 /* Private methods */
551 this.JpegMeta
.JpegFile
.prototype._addMetaGroup
= function _addMetaGroup(name
, description
) {
552 var group
= new JpegMeta
.MetaGroup(name
, description
);
553 this[group
.fieldName
] = group
;
554 this.metaGroups
[group
.fieldName
] = group
;
558 this.JpegMeta
.JpegFile
.prototype._parseIfd
= function _parseIfd(endian
, _binary_data
, base
, ifd_offset
, tags
, name
, description
) {
559 var num_fields
= JpegMeta
.parseNum(endian
, _binary_data
, base
+ ifd_offset
, 2);
560 /* Per tag variables */
564 var type
, type_field
, type_size
;
574 group
= this._addMetaGroup(name
, description
);
576 for (var i
= 0; i
< num_fields
; i
++) {
577 /* parse the field */
578 tag_base
= base
+ ifd_offset
+ 2 + (i
* 12);
579 tag_field
= JpegMeta
.parseNum(endian
, _binary_data
, tag_base
, 2);
580 type_field
= JpegMeta
.parseNum(endian
, _binary_data
, tag_base
+ 2, 2);
581 num_values
= JpegMeta
.parseNum(endian
, _binary_data
, tag_base
+ 4, 4);
582 value_offset
= JpegMeta
.parseNum(endian
, _binary_data
, tag_base
+ 8, 4);
583 if (this._types
[type_field
] === undefined) {
586 type
= this._types
[type_field
][0];
587 type_size
= this._types
[type_field
][1];
589 if (type_size
* num_values
<= 4) {
590 /* Data is in-line */
591 value_offset
= tag_base
+ 8;
593 value_offset
= base
+ value_offset
;
597 if (type
== "UNDEFINED") {
598 value
= _binary_data
.slice(value_offset
, value_offset
+ num_values
);
599 } else if (type
== "ASCII") {
600 value
= _binary_data
.slice(value_offset
, value_offset
+ num_values
);
601 value
= value
.split('\x00')[0];
602 /* strip trail nul */
605 for (j
= 0; j
< num_values
; j
++, value_offset
+= type_size
) {
606 if (type
== "BYTE" || type
== "SHORT" || type
== "LONG") {
607 value
.push(JpegMeta
.parseNum(endian
, _binary_data
, value_offset
, type_size
));
609 if (type
== "SBYTE" || type
== "SSHORT" || type
== "SLONG") {
610 value
.push(JpegMeta
.parseSnum(endian
, _binary_data
, value_offset
, type_size
));
612 if (type
== "RATIONAL") {
613 num
= JpegMeta
.parseNum(endian
, _binary_data
, value_offset
, 4);
614 den
= JpegMeta
.parseNum(endian
, _binary_data
, value_offset
+ 4, 4);
615 value
.push(new JpegMeta
.Rational(num
, den
));
617 if (type
== "SRATIONAL") {
618 num
= JpegMeta
.parseSnum(endian
, _binary_data
, value_offset
, 4);
619 den
= JpegMeta
.parseSnum(endian
, _binary_data
, value_offset
+ 4, 4);
620 value
.push(new JpegMeta
.Rational(num
, den
));
624 if (num_values
=== 1) {
628 if (tags
[tag_field
] !== undefined) {
629 group
._addProperty(tags
[tag_field
][1], tags
[tag_field
][0], value
);
634 this.JpegMeta
.JpegFile
.prototype._jfifHandler
= function _jfifHandler(mark
, pos
) {
635 if (this.jfif
!== undefined) {
636 throw Error("Multiple JFIF segments found");
638 this._addMetaGroup("jfif", "JFIF");
639 this.jfif
._addProperty("version_major", "Version Major", this._binary_data
.charCodeAt(pos
+ 5));
640 this.jfif
._addProperty("version_minor", "Version Minor", this._binary_data
.charCodeAt(pos
+ 6));
641 this.jfif
._addProperty("version", "JFIF Version", this.jfif
.version_major
.value
+ "." + this.jfif
.version_minor
.value
);
642 this.jfif
._addProperty("units", "Density Unit", this._binary_data
.charCodeAt(pos
+ 7));
643 this.jfif
._addProperty("Xdensity", "X density", JpegMeta
.parseNum(">", this._binary_data
, pos
+ 8, 2));
644 this.jfif
._addProperty("Ydensity", "Y Density", JpegMeta
.parseNum(">", this._binary_data
, pos
+ 10, 2));
645 this.jfif
._addProperty("Xthumbnail", "X Thumbnail", JpegMeta
.parseNum(">", this._binary_data
, pos
+ 12, 1));
646 this.jfif
._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta
.parseNum(">", this._binary_data
, pos
+ 13, 1));
649 /* Handle app0 segments */
650 this.JpegMeta
.JpegFile
.prototype._app0Handler
= function app0Handler(mark
, pos
) {
651 var ident
= this._binary_data
.slice(pos
, pos
+ 5);
652 if (ident
== this._JFIF_IDENT
) {
653 this._jfifHandler(mark
, pos
);
654 } else if (ident
== this._JFXX_IDENT
) {
655 /* Don't handle JFXX Ident yet */
657 /* Don't know about other idents */
661 /* Handle app1 segments */
662 this.JpegMeta
.JpegFile
.prototype._app1Handler
= function _app1Handler(mark
, pos
) {
663 var ident
= this._binary_data
.slice(pos
, pos
+ 5);
664 if (ident
== this._EXIF_IDENT
) {
665 this._exifHandler(mark
, pos
+ 6);
667 /* Don't know about other idents */
671 /* Handle exif segments */
672 JpegMeta
.JpegFile
.prototype._exifHandler
= function _exifHandler(mark
, pos
) {
673 if (this.exif
!== undefined) {
674 throw new Error("Multiple JFIF segments found");
677 /* Parse this TIFF header */
681 var primary_ifd
, exif_ifd
, gps_ifd
;
682 var endian_field
= this._binary_data
.slice(pos
, pos
+ 2);
684 /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
685 if (endian_field
=== "II") {
687 } else if (endian_field
=== "MM") {
690 throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field
);
693 magic_field
= JpegMeta
.parseNum(endian
, this._binary_data
, pos
+ 2, 2);
695 if (magic_field
!== 42) {
696 throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field
);
699 ifd_offset
= JpegMeta
.parseNum(endian
, this._binary_data
, pos
+ 4, 4);
702 this._parseIfd(endian
, this._binary_data
, pos
, ifd_offset
, this._tifftags
, "tiff", "TIFF");
704 if (this.tiff
.ExifIfdPointer
) {
705 this._parseIfd(endian
, this._binary_data
, pos
, this.tiff
.ExifIfdPointer
.value
, this._exiftags
, "exif", "Exif");
708 if (this.tiff
.GPSInfoIfdPointer
) {
709 this._parseIfd(endian
, this._binary_data
, pos
, this.tiff
.GPSInfoIfdPointer
.value
, this._gpstags
, "gps", "GPS");
710 if (this.gps
.GPSLatitude
) {
712 latitude
= this.gps
.GPSLatitude
.value
[0].asFloat() +
713 (1 / 60) * this.gps
.GPSLatitude
.value
[1].asFloat() +
714 (1 / 3600) * this.gps
.GPSLatitude
.value
[2].asFloat();
715 if (this.gps
.GPSLatitudeRef
.value
=== "S") {
716 latitude
= -latitude
;
718 this.gps
._addProperty("latitude", "Dec. Latitude", latitude
);
720 if (this.gps
.GPSLongitude
) {
722 longitude
= this.gps
.GPSLongitude
.value
[0].asFloat() +
723 (1 / 60) * this.gps
.GPSLongitude
.value
[1].asFloat() +
724 (1 / 3600) * this.gps
.GPSLongitude
.value
[2].asFloat();
725 if (this.gps
.GPSLongitudeRef
.value
=== "W") {
726 longitude
= -longitude
;
728 this.gps
._addProperty("longitude", "Dec. Longitude", longitude
);
733 // MediaWiki: Export as module
734 module
.exports = function( fileReaderResult
, fileName
) {
735 return new JpegMeta
.JpegFile( fileReaderResult
, fileName
);
738 // MediaWiki: Add mw.libs wrapper
739 // @deprecated since 1.31
740 mw
.log
.deprecate( mw
.libs
, 'jpegmeta', module
.exports
);